package com.zxiw.zxedu.datasync

import android.util.Log
import kotlinx.coroutines.Job
import kotlinx.coroutines.runBlocking
import java.util.*

/**
 * @author zhangxiaoming
 * @date 2023/8/9
 * @description 任务启动器
 */
object XTaskStarter: XTaskLogger {
    /**
     * 开始执行project中的任务
     * runBlocking 会阻塞线程，如果是在主线程调用，则必须小心ANR
     * @param project IXTaskProject
     */
    @JvmStatic
    fun start(project: IXTaskProject) = runBlocking {
        //获取按优先级排序的列表
        val taskList: List<XTask> = project.getTasks()
        //缓存完成的任务
        val finishedTaskList: MutableList<XTask> = mutableListOf()
        // 找出所有入度为0的任务，加入到队列当中
        val queue: Queue<XTask> = LinkedList()
        taskList.filter { it.inDegree == 0 }.forEach(queue::add)
        //建立一个map, 通过name可以获取协程的Job
        val jobMap = mutableMapOf<String, Job>()
        //循环执行队列里的任务
        Log.i { "Start assigning tasks, current time ${System.currentTimeMillis()}" }
        var totalCostTime = 0L
        //添加第一个活动节点
        val plantUMLCreator = PlantUMLCreator()
        plantUMLCreator.reset()
        if (queue.size > 1) {
            val parallelTasks = mutableListOf<String>()
            for (task in queue) {
                parallelTasks.add(task.name)
            }
            plantUMLCreator.addParallelTask(parallelTasks)
        } else {
            queue.peek()?.let {
                plantUMLCreator.addSingleTask(it.name)
            }
        }
        while (queue.isNotEmpty()) {
            //获取当前需要执行的任务
            val currentTask = queue.poll()!!
            logger.info { "--- Current assigned task is $currentTask" }
            //在指定的协程调度器启动一个协程，并返回Job
            jobMap[currentTask.name] = launch(currentTask.coroutineDispatcher) {
                //遍历当前任务依赖的任务集合
                for (dependentTask in currentTask.dependentTasks) {
                    //使用给定的协程上下文调用指定的挂起块，挂起直到完成，并返回结果。
                    withContext(currentTask.coroutineDispatcher) { // 这句代码很重要，不然会有死锁，想一想为什么？
                        jobMap[dependentTask]!!.join() // 依赖的任务必须先执行完，因为这个是拓扑排序执行的，所以理论上jobMap[dep]不可能为空，当然有可能填错任务名称的
                    }
                }
                //依赖已经执行完成，执行自身的任务
                val startTime = System.currentTimeMillis()
                logger.info { ">>>>>> Task ${currentTask.name} start <<<<<<" }
                val intercept = currentTask.run()
                logger.info { "Task ${currentTask.name} notifyObservers $intercept" }
                val endTime = System.currentTimeMillis()
                val costTime = (endTime - startTime)
                totalCostTime += costTime
                logger.info { "<<<<<< Task ${currentTask.name} completed, cost $costTime ms >>>>>>" }
                //添加到任务完成集合
                finishedTaskList.add(currentTask)
                //通知观察者
                currentTask.notify(intercept)
                //更新节点耗时
                plantUMLCreator.getPlantUMLActivityNode(currentTask.name)?.let {
                    logger.info { "${currentTask.name} getPlantUMLActivityNode ${it.elements}" }
                    logger.info { "${currentTask.name} costTime $costTime" }
                    it.elements[currentTask.name]?.costTime = costTime
                }
                //如果拦截，则取消剩余未完成的job
                if (intercept) {
                    project.release()
                    queue.clear()
                    for ((taskName, job) in jobMap) {
                        if (job.isActive) {
                            job.cancel()
                            logger.info { "Task $taskName $job cancel" }
                        }
                    }
                    jobMap.clear()
                    logger.warn { "============ Task ${currentTask.name} intercept, clear queue and cancel all jobs " }
                }
                logger.info { "${currentTask.name} taskList size ${taskList.size}  finishedTaskList size ${finishedTaskList.size} " }
                if (taskList.size == finishedTaskList.size) {
                    logger.info { "All tasks completed cost $totalCostTime ms " }
                    //保存
                    logger.info { "All tasks completed cost $totalCostTime ms " }
                    plantUMLCreator.create()
                }
            }
            //当前任务已经开始分配，找到所有依赖当前任务的任务，将它们的入度减1，如果入度为0，则加入队列
            val toBeOfferTasks = mutableListOf<XTask>()
            //记录当前任务是否有被其它任务依赖
            var currentTaskHasDependentTasks = false
            for (task in taskList) {
                if (task.dependentTasks.contains(currentTask.name)) {
                    currentTaskHasDependentTasks = true
                    //入度减1
                    task.inDegree--
                    //入度为0，则进入队列
                    if (task.inDegree == 0) {
                        toBeOfferTasks.add(task)
                    }
                }
            }
            logger.info { "${currentTask.name} currentTaskHasDependentTasks $currentTaskHasDependentTasks toBeOfferTasks $toBeOfferTasks" }
            //修改任务end标记
            plantUMLCreator.getPlantUMLActivityNode(currentTask.name)?.apply {
                elements[currentTask.name]?.end = !currentTaskHasDependentTasks
            }
            //添加下一个活动节点
            if (toBeOfferTasks.isNotEmpty()) {
                if (toBeOfferTasks.size > 1) {
                    val parallelTasks = mutableListOf<String>()
                    for (task in toBeOfferTasks) {
                        parallelTasks.add(task.name)
                    }
                    logger.info { "${currentTask.name} addParallelTask $parallelTasks" }
                    plantUMLCreator.addParallelTask(parallelTasks)
                } else {
                    toBeOfferTasks[0].let {
                        logger.info { "${currentTask.name} addSingleTask ${it.name}" }
                        plantUMLCreator.addSingleTask(it.name)
                    }
                }
                //将入度为零的任务加入队列
                for (task in toBeOfferTasks) {
                    queue.offer(task)
                }
            }
        }
        // 这个地方需要判断一下，是否所有的任务都已经被安排执行了，如果还有任务没有被安排，说明任务存在循环依赖，抛出异常。
        if (taskList.isNotEmpty() && jobMap.isNotEmpty() && jobMap.size != taskList.size) {
            //循环结束后，如果有任务没有安排，则jobMap数量肯定是不等于taskList的数量
            throw Throwable("Exist Recycle Task!")
        }
        Log.i { "All task assignments completed" }
    }
}
